

#ifndef SSOCKET_H_
#define SSOCKET_H_

#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>


#ifdef __cplusplus

extern "C" {
#endif


/**
 * @brief Add socket options by calling setsockopt
 *
 * @param _fd Socket file descriptor
 * @param _addr2Bind The struct sockaddr to bind on
 * @param _addrlen Length of _addr2Bind
 * @param _sendTimeOut The struct timeval to specify the sending timeout.
 * @param _recvTimeOut The struct timeval to specify the receiving timeout.
 *
 * @return Return 0 for success and -1 for error.
 */
int processSocket( int _fd, const struct sockaddr *_addr2Bind, const socklen_t _addrlen, const struct timeval *_sendTimeOut, const struct timeval *_recvTimeOut) {

	if( _fd <= 0) {
		return (int)-1;
	}
	/* short-circuit condition test */
	if( _sendTimeOut != NULL && _sendTimeOut->tv_sec >= 0 && _sendTimeOut->tv_usec >= 0) { 
		if(setsockopt( _fd, SOL_SOCKET, SO_SNDTIMEO, _sendTimeOut, (socklen_t)sizeof(struct timeval)) == -1) {
			return (int)-1;
		}
	}
	if( _recvTimeOut != NULL && _recvTimeOut->tv_sec >= 0 && _recvTimeOut->tv_usec >= 0) {
		if(setsockopt( _fd, SOL_SOCKET, SO_RCVTIMEO, _recvTimeOut, (socklen_t)sizeof(struct timeval)) == -1) {
			return (int)-1;
		}
	}

	int flags = 1;
	if(setsockopt( _fd, SOL_SOCKET, SO_REUSEADDR, &flags, (socklen_t)sizeof(int)) == -1) {
		return (int)-1;
	}
	if( _addr2Bind != NULL && _addrlen >= (socklen_t)sizeof(struct sockaddr) && bind( _fd, _addr2Bind, _addrlen) == -1) {
		return (int)-1;
	}

	return 0;
}



/**
 * @brief Create a tcp socket and bing it to a port.
 *
 * @param _addr2Bind The struct sockaddr to specify the binding address.
 * @param _addrlen Length of _addr2Bind
 * @param _sendTimeOut The struct timeval to specify timeout.
 * @param _recvTimeOut The struct timeval to specify timeout.
 *
 * @return Return normal file descriptor for success and -1 for error.
 */
int tcpSocket( const struct sockaddr *_addr2Bind, const socklen_t _addrlen, const struct timeval *_sendTimeOut, const struct timeval *_recvTimeOut) {

	int fd	= 0;

	fd = socket( AF_INET, SOCK_STREAM, 0); /** Let system decide a proper protocal for TCP.  */
	if(fd == -1) {
		return (int)-1;
	}

	if(processSocket(fd, _addr2Bind, _addrlen, _sendTimeOut, _recvTimeOut) == -1) {
		return (int)-1;
	}

	return fd;
}


/**
 * @brief Create a UDP socket and bind it on a port. (Please refer to tcpSocket() for arguments details.)
 * [note] This method hasn't been verified yet, but it should have the same behavior with tcpSocket since
 * they share almost the same code.
 *
 * @return Return UDP socket file descriptor for success and -1 for error.
 */
int udpSocket( const struct sockaddr *_addr2Bind, const socklen_t _addrlen, const struct timeval *_sendTimeOut, const struct timeval *_recvTimeOut) {

	int fd	= 0;

	fd = socket( AF_INET, SOCK_DGRAM, 0); /** Let system decide a proper protocal for TCP.  */
	if(fd == -1) {
		return (int)-1;
	}

	if(processSocket(fd, _addr2Bind, _addrlen, _sendTimeOut, _recvTimeOut) == -1) {
		return (int)-1;
	}

	return fd;
}


/**
 * @brief Macro definition for accept(). I use macro here for efficiency.
 * Originally, I use it as a wrapper for accept(). Under Windows, it should
 * be changed to accommondate the Winsock 2.0. However, since we have used 
 * the wrapper, we may not have to change to sources that use acceptSocket().
 *
 * @param _fd File descriptor for socket.
 * @param _addr struct sockaddr_in*
 * @param _addr sockelen_t*
 *
 * @return Returned value is aligned with accept.
 */
#define acceptSocket(_fd,_addr,_len)	accept(_fd,_addr,_len)

/**
 * @brief Macro wrapper for listen().
 *
 * @param _fd File descriptor to listen on.
 * @param _backlog The minimum number of input socket that listen queue should 
 * keep.
 *
 * @return Returned value align with listen. 
 */
#define listenSocket(_fd,_backlog)	listen(_fd,_backlog)


#define shutdownRd(_fd)			shutdown(_fd,SHUT_RD)
#define shutdownWr(_fd)			shutdown(_fd,SHUT_WR)
#define shutdownDrWr(_fd)		shutdown(_fd,SHUT_RDWR)

/**
 * @brief Create a raw socket.
 * [note] This method hasn't been implemented, because I have not decided whether I need a raw socket.
 *
 * @return  Return file descriptor to socket for success and -1 for error.
 */
int rawSocket() {
	return -1;
}


/**
 * @brief Set socket file descriptor non-block.
 *
 * @param _fd Socket file descriptor.
 *
 * @return Returned value aligned with fcntl().
 */
int unBlockSocket(int _fd) {
	return fcntl( _fd, F_SETFL, fcntl( _fd, F_GETFL)|O_NONBLOCK);
}

#ifdef __cplusplus

}
#endif



#endif /* SSOCKET_H_ */
